/*                                                                         
 * Copyright 2017-2017 the original author or authors.                     
 *                                                                         
 * Licensed under the Apache License, Version 2.0 (the "License");         
 * you may not use this file except in compliance with the License.        
 * You may obtain a copy of the License at                                 
 *                                                                         
 *      http://www.apache.org/licenses/LICENSE-2.0                         
 *                                                                         
 * Unless required by applicable law or agreed to in writing, software     
 * distributed under the License is distributed on an "AS IS" BASIS,       
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and     
 * limitations under the License.                                          
 */
package com.shopng.utils;

/*                                                                         
 * Copyright 2010-2013 the original author or authors.                     
 *                                                                         
 * Licensed under the Apache License, Version 2.0 (the "License");         
 * you may not use this file except in compliance with the License.        
 * You may obtain a copy of the License at                                 
 *                                                                         
 *      http://www.apache.org/licenses/LICENSE-2.0                         
 *                                                                         
 * Unless required by applicable law or agreed to in writing, software     
 * distributed under the License is distributed on an "AS IS" BASIS,       
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and     
 * limitations under the License.                                          
 */
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;

import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.geode.internal.net.SocketCreator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.LoggerContext;
import org.springframework.util.PropertyPlaceholderHelper;

/**
 * 
 * @author sgao20
 *
 */
public class ConfigHelper implements ConfigurationConstants
{
  
  // env file can be setted by user， default env.properties
  private static final String ENV_FILE_NAME = "env.file";
  private static final String LOG4J_DEFAULT_CONFILE = "log4j2-server.xml";
  // envfile
  public static String ENV_FILE = System.getProperty(ENV_FILE_NAME, "env.properties");
  
  // which Properties Keys can be set to System Environment
  private static final AtomicBoolean initlized = new AtomicBoolean(false);
  private static final AtomicBoolean initlizing = new AtomicBoolean(false);
  private static final AtomicBoolean initlized_log = new AtomicBoolean(false);
  
  private static int locatorCount = 0;
  static Properties serverConfig;
  static Properties defaultConfig;
  static PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${", "}");
  
  static
  {
    serverConfig = new Properties();
    defaultConfig=new Properties();
  }
  
  public static void main(String[] args)
  {
    System.out.println(">>"+ ConfigHelper.getVersion());
  }
  public static String getVersion()
  {
    String version = "";
    InputStream in = ConfigHelper.class.getClassLoader().getResourceAsStream("application.properties");
    Properties prop = new Properties();
    try
    {
      prop.load(in);
      version = prop.getProperty("version");
    }
    catch (IOException e)
    {
    }
    return version;
  }
  
  public static String getConfig(ITEMS item)
  {
    if (serverConfig != null && serverConfig.containsKey(item.name()))
    {
      return serverConfig.getProperty(item.getProKey());
    }
    return System.getProperty(item.getProKey());
  }
  
  private static ITEMS stringToITEMS(String name)
  {
    ITEMS item = ITEMS.nameMap.get(name);
    if (item == null)
      item = ITEMS.proKeyMap.get(name);
    return item;
  }
  
  // setProperty
  public static void setProperty(String name, Object value)
  {
    if (value == null)
      return;
      
    ITEMS item = stringToITEMS(name);
    if (item != null)
      setProperty(item, value);
    else
    {
      if (initlized.get())
      {
        Object old = serverConfig.get(name);
        if (value.equals(old))
          return;
        LogUtil.getCoreLog().info("Set {}={}", name, value);
      }
      serverConfig.put(name, value);
    }
    
  }
  
  public static void setProperty(ITEMS item, Object value)
  {
    
    if (initlized.get())
    {
      Object old = serverConfig.get(item.proKey);
      if (value.equals(old))
        return;
      LogUtil.getCoreLog().info("Set {}={}\t//{}", item.proKey, value, item.name());
    }
    
    serverConfig.put(item.proKey, value);
    serverConfig.put(item.name(), value);
    
  }
  
  /**
   * put all after init.
   * 
   * @param pro
   */
  public static void putAll(Properties pro)
  {
    if (initlized.get())
      LogUtil.getCoreLog().info("ADF config Helper putAll additinal properties= {}.", pro);
    if (pro != null)
    {
      Iterator<Map.Entry<Object, Object>> entryIt = pro.entrySet().iterator();
      while (entryIt.hasNext())
      {
        Map.Entry<Object, Object> entry = entryIt.next();
        setProperty((String) entry.getKey(), (String) entry.getValue());
      }
    }
  }
  
  public static void initLog4j()
  {
    initLog4j(LOG4J_DEFAULT_CONFILE);
  }
  
  public static void initLog4j(String configFile)
  {
    if (initlized_log.get())
      return;
    initlized_log.set(true);
    ThreadContext.put(ITEMS.GS_HOME.proKey, ConfigHelper.getConfig(ITEMS.GS_HOME));
    ThreadContext.put(ITEMS.NODE_TYPE.proKey, ConfigHelper.getConfig(ITEMS.NODE_TYPE));
    ThreadContext.put(ITEMS.NODE_NAME.proKey, ConfigHelper.getConfig(ITEMS.NODE_NAME));
    System.setProperty("Log4jContextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
    configFile = LOG4J_DEFAULT_CONFILE;
    URL url;
    try
    {
      url = ConfigHelper.class.getClassLoader().getResource(configFile);
      LoggerContext context = (LoggerContext) LogManager.getContext(false);
      LogUtil.getCoreLog().info("Log4j config file loading from {}.", url);
      context.setConfigLocation(url.toURI());
    }
    catch (URISyntaxException e)
    {
      System.err.print(e.getMessage());
      initlized_log.set(false);
    }
    initlized_log.set(true);
  }
  
  @SuppressWarnings("unchecked")
  public static void dump()
  {
    StringBuilder bu = new StringBuilder("\n=======================ADF config==============================\n");
    @SuppressWarnings("rawtypes")
    List list = new ArrayList<>(serverConfig.keySet());
    Collections.sort(list);
    List priorList = new ArrayList<>();
    defaultConfig.forEach(new BiConsumer<Object,Object>()
    {
      @Override
      public void accept(Object t, Object u)
      {
        if(list.contains(t))
        {
          Object x =serverConfig.getProperty(t.toString());
          if(!x.equals(u))
            priorList.add(t);
        }
      }
    });
    Collections.sort(priorList);
    for(Object key:priorList)
    {
      if (!ITEMS.nameMap.containsKey(key))
      {
        ITEMS item = ITEMS.proKeyMap.get(key);
        Object value = serverConfig.getProperty(key.toString());
        if (item != null)
          bu.append(key).append("=").append(value).append("\t//").append(item.name()).append("\n");
        else
          bu.append(key).append("=").append(value).append("\n");
      }
    }
    bu.append("===============================================================\n");
    for (Object key : list)
    {
      if(priorList.contains(key))
        continue;
      if (!ITEMS.nameMap.containsKey(key))
      {
        ITEMS item = ITEMS.proKeyMap.get(key);
        Object value = serverConfig.getProperty(key.toString());
        if (item != null)
          bu.append(key).append("=").append(value).append("\t//").append(item.name()).append("\n");
        else
          bu.append(key).append("=").append(value).append("\n");
      }
    }
    bu.append("===============================================================");
    LogUtil.getCoreLog().info(bu.toString());
  }
  
  public static void initConfig()
  {
    initConfig(ENV_FILE);
    
  }
  
  public static void initConfig(String envFile)
  {
    if (initlized.get())
      return;
    try
    {
      Enumeration<URL> urls = ConfigHelper.class.getClassLoader().getResources(envFile);
      while (urls.hasMoreElements())
      {
        System.out.println("Found env url:" + urls.nextElement());
      }
      URL envFileURL = ConfigHelper.class.getClassLoader().getResource(envFile);
      initConfig(envFileURL, null);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      System.exit(-10);
    }
  }
  
  /**
   * get config
   * @param envFile
   * @return
   */
  public static Properties getConfig(String envFile)
  {
    try
    {
      Enumeration<URL> urls = ConfigHelper.class.getClassLoader().getResources(envFile);
      while (urls.hasMoreElements())
      {
        System.out.println("Found env url:" + urls.nextElement());
      }
      URL envFileURL = ConfigHelper.class.getClassLoader().getResource(envFile);
      Properties prop = new Properties();
      prop.load(envFileURL.openStream());
      return prop;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      LogUtil.getCoreLog().error("load file error:{}",e);
    }
    return null;
  }
  
  public static void initConfig(URL envFileURL)
  {
    initConfig(envFileURL, null);
  }
  
  public static void initConfig(Properties envProperties)
  {
    initConfig(null, envProperties);
  }
  
  private static void initConfig(URL envFileURL, Properties envProperties)
  {
    if (initlizing.get() || initlized.get())
      return;
    initlizing.set(true);
    try
    {
      beforeConfigInitial();
      initConfig0(envFileURL, envProperties);
      afterConfigInitial();
    }
    catch (IOException e)
    {
      initlized.set(false);
      e.printStackTrace();
      System.exit(-1);
    }
    initlized.set(true);
    dump();
  }

  /**
   *
   */
  private static void  beforeConfigInitial(){}

  /**
   *
   */
  private static void  afterConfigInitial(){
       //create log directory when not exist.
       WorkPathHelper.verifyPath("log");


  }

  /***
   * config file load order
   * 1. defaultEnvFile
   * 2. envFile
   * 3. System.env
   * 4. Systen.properties
   * 
   * @param defaultEnvFile
   * @param envFileURL
   * @throws IOException
   */
  private synchronized static void initConfig0(URL envFileURL, Properties envProperties)
      throws IOException
  {
    if (envProperties != null)
    {
      putAll(envProperties);
      System.out.println("Load env from given properties, properties items:" + envProperties.size());
    }
    else if (envFileURL != null)
    {
      Properties prop = new Properties();
      prop.load(envFileURL.openStream());
      putAll(prop);
      System.out.println("Load env from:" + envFileURL);
    }
    
    // set config to System Properties
    Iterator<Object> it = serverConfig.keySet().iterator();
    while (it.hasNext())
    {
      String key = it.next().toString();
      processSystemEnv(key);
    }
    System.setProperty("gemfire.ALLOW_PERSISTENT_TRANSACTIONS", "true");
    String bindip = getConfig(ITEMS.BINDIP);
    processBindIp(bindip);
    String locators = getProperty(ITEMS.LOCATORS);
    processLocators(locators);

    replacePlaceholders();
    WorkPathHelper.verifyPath("log");
  }
  
  @SuppressWarnings("rawtypes")
  private static void replacePlaceholders()
  {
    Iterator entries = serverConfig.entrySet().iterator();
    while (entries.hasNext())
    {
      Entry e = (Entry) entries.next();
      Object value = e.getValue();
      String replaced = helper.replacePlaceholders(value.toString(), serverConfig);
      if (replaced != null && !replaced.equals(value))
      {
        setProperty(e.getKey().toString(), replaced);
      }
    }
  }
  
  public static void processLocators(String locators)
  {
    if (!StringUtils.isEmpty(locators))
    {
      String[] grp = locators.split(",");
      locatorCount = grp.length;
      for (int i = 0; i < grp.length; i++)
      {
        String[] sub = grp[i].split("\\[");
        String ip = sub[0];
        String port = sub[1].substring(0, sub[1].length() - 1);
        String key1 = ITEMS.LOCATOR.name() + (i + 1);
        String key2 = ITEMS.PORT.name() + (i + 1);
        setProperty(key1, ip);
        setProperty(key2, Integer.valueOf(port));
      }
    }
  }
  
  public static String processLocalhost(String locators)
  {
    if (!StringUtils.isEmpty(locators) && locators.indexOf("localhost") != -1)
    {
      String ip = localhostBindIp(null);
      locators = locators.replaceAll("localhost", ip);
    }
    return locators;
  }
  
  public static String localhostBindIp(String bindIp)
  {
    boolean isLocalhost = bindIp == null || "localhost".equalsIgnoreCase(bindIp);
    if (isLocalhost)
    {
      try
      {
        InetAddress addr = InetAddress.getLocalHost();
        bindIp = addr.getHostAddress();
      }
      catch (UnknownHostException e)
      {
        initlized.set(false);
        System.err.println("Unkonw host!");
        return null;
      }
    }
    return bindIp;
  }
  
  private static void processBindIp(String bindIp)
  {
    bindIp = localhostBindIp(bindIp);
    if (!StringUtils.isEmpty(bindIp))
    {
      setProperty(ITEMS.BINDIP, bindIp);
      String locators = getConfig(ITEMS.LOCATORS);
      locators = locators.replaceAll("localhost", bindIp);
      setProperty(ITEMS.LOCATORS, locators);
    }
  }
  
  /***
   * 
   * @param name
   */
  private static void processSystemEnv(String name)
  {
    
    String value = System.getProperty(name);
    if (StringUtils.isEmpty(value))
    {
      value = System.getenv(name);
    }
    if (!StringUtils.isEmpty(value))
    {
      setProperty(name, value);
    }
  }
  
  private final static String locatorFlag = "-l-";
  private final static String serverFlag = "-s-";
  
  public static void setMemberName(String serverName)
  {
    String hostName = "localhost";
    try
    {
      hostName = SocketCreator.getLocalHost().getHostName();
    }
    catch (UnknownHostException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    ConfigHelper.setProperty(ConfigHelper.ITEMS.HOST_NAME.name(), hostName);
    ConfigHelper.setProperty(ConfigHelper.ITEMS.NODE_NAME.name(), serverName);
    StringBuilder bui = new StringBuilder();
    ConfigHelper.setProperty(ITEMS.MEMBER_NAME, serverName);
  }
  
  public static String getMemberName()
  {
    return getConfig(ITEMS.MEMBER_NAME);
  }
  
  public static String getMemberId()
  {
    return getConfig(ITEMS.MEMBER_ID);
  }
  
  public static String getNodeName()
  {
    return getConfig(ITEMS.NODE_NAME);
  }
  
  public static String getWorkPath()
  {
    return getConfig(ITEMS.GS_WORK);
  }
  
  public static String getHomePath()
  {
    return getConfig(ITEMS.GS_HOME);
  }
  
  public static String getNodeType()
  {
    return getConfig(ITEMS.NODE_TYPE);
  }
  
  public static String getProperty(ITEMS item)
  {
    return getProperty(item, "");
  }
  
  public static String getProperty(ITEMS item, String defaultValue)
  {
    String value = serverConfig.getProperty(item.name());
    if (value == null)
      value = serverConfig.getProperty(item.proKey);
    if (value == null)
      value = defaultValue;
    return value;
  }
  
  public static String getDefaultLocatorAdddress()
  {
    if (locatorCount == 0)
      return null;
    return getProperty(ITEMS.LOCATOR.name() + "1");
    
  }
  
  public static Integer getDefaultLocatorPort()
  {
    if (locatorCount == 0)
      return null;
    Object port = getInteger(ITEMS.PORT.name() + "1");
    if (port instanceof Integer)
    {
      return (Integer) port;
    }
    else
    {
      return NumberUtils.toInt(port.toString());
    }
    
  }
  
  public static String getProperty(String name)
  {
    ITEMS item = stringToITEMS(name);
    if (item != null)
      return getProperty(item);
    else
      return serverConfig.getProperty(name);
  }
  
  public static String getProperty(String name, String defaultValue)
  {
    String value = getProperty(name);
    if (value == null)
      return defaultValue;
    return value;
  }
  
  public static Integer getInteger(String name)
  {
    Object val = getProperty(name);
    if (val instanceof Integer)
      return (Integer) val;
    else if (val != null)
      return NumberUtils.toInt(val.toString());
    else
      return null;
  }
  
  public static Integer getInteger(ITEMS item)
  {
    Object val = getProperty(item);
    if (val instanceof Integer)
      return (Integer) val;
    else if (val != null)
      return NumberUtils.toInt(val.toString());
    else
      return null;
  }
  
  public static boolean getBoolean(ITEMS item)
  {
    String booleanStr = getConfig(item);
    return BooleanUtils.toBoolean(booleanStr);
  }

  public static  boolean getBoolean (String name){

    String value  = getProperty(name);
    return BooleanUtils.toBoolean(value);
  }

  public static int getLocatorCount()
  {
    return locatorCount;
  }
  
  /**
   * Return whether to dev development mode
   *
   * @return
   */
  public static boolean isDev()
  {
    String env = ConfigHelper.getConfig(ITEMS.GS_ENV);
    return StringUtils.equalsIgnoreCase("dev", env);
  }
  
  public enum TYPES
  {
      DATASTORE, LOCATOR, JMX, MQ, CLIENT
  }
  
  public enum ITEMS
  {
      GS_ENV(ConfigurationConstants.GS_ENV), GS_HOME(ConfigurationConstants.GS_HOME),
      GS_WORK(ConfigurationConstants.GS_WORK), GS_VERSION(ConfigurationConstants.GS_VERSION),
      SYSTEM_NAME(ConfigurationConstants.SYSTEM_NAME), SYSTEM_GRIDNAME(ConfigurationConstants.SYSTEM_GRIDNAME),
      LOCATORS(ConfigurationConstants.GS_LOCATORS), BINDIP(ConfigurationConstants.GS_BINDIP),SYSTEM_WAN_AREA("system.wan.area"),
      HTTP_PLUGIN_BIND_ADDRESS("http.plugin.bind.address"), HTTP_PLUGIN_PORT("http.plugin.port"),
      SOLR_SERVERS(ConfigurationConstants.SOLR_ZK_HOST), ZOOKEEPER_SERVERS(ConfigurationConstants.ZOOKEEPER_SERVERS),
      ADF_SESSION_TIMEOUT(ConfigurationConstants.ADF_SESSION_TIMEOUT), GS_LOGIC_JAR_DIR("gs.logicJarDir"),
      DEPLOY_WORKING_DIR("gs.deployWorkingDir"), NODE_NAME("gs.nodeName"),NODE_WORK_DIR("gs.node-workDir"), HOST_NAME("gs.hostName"),
      MEMBER_NAME("gs.memberName"), LOCATOR("gs.locator"), PORT("gs.port"), NODE_TYPE("gs.nodeType"),
      MEMBER_ID("gs.memberId"), DB_TYPE(ConfigurationConstants.DB_TYPE),
      DB_NAME(ConfigurationConstants.DB_DEFAULT_NAME), DB_URL(ConfigurationConstants.DB_URL),
      DB_PORT(ConfigurationConstants.DB_PORT), DB_DIRVER(ConfigurationConstants.DB_DRIVER),
      DB_USERNAME(ConfigurationConstants.DB_USERNAME), DB_PASSWORD(ConfigurationConstants.DB_PASSWORD),
      DB_DIALECT(ConfigurationConstants.DB_DIALECT), JMX_HOST(ConfigurationConstants.JMX_HOST),
      JMX_PORT(ConfigurationConstants.JMX_PORT), HTML_PORT(ConfigurationConstants.HTML_PORT),
      JMX_RMI_PORT(ConfigurationConstants.JMX_RMI_PORT), JMX_MANAGER_PORT("jmx.manager.port"),
      NAMING_SERVER(ConfigurationConstants.NAMING_SERVER), GRID_OPLOG_META(ConfigurationConstants.GRID_OPLOG_META),
      GRID_OPLOG_QUEUE(ConfigurationConstants.GRID_OPLOG_QUEUE),
       GRID_META_VISIBLE("grid.meta.visible"),
      GRID_STATUS_THREAD_INTERVAL(ConfigurationConstants.GRID_STATUS_THREAD_INTERVAL),
      CACHE_REAL_TIME(ConfigurationConstants.GRID_CACHE_REALTIME),
      CLINET_MULTIPLE_USER(ConfigurationConstants.CLIENT_NULTIPLE_USER_ENABLED),
      STATISTICS_ENABLED("gs.statistics.enabled"), TEMPORAL_UNIT("gs.temporal.unit"), GRID_AUTHENTICATION_ENABLED("security.grid.authenticate.enabled"),
      AUTH_MODE("security.auth.mode"),CLUSTER_REMOTE_LOCATORS("cluster.remote.locators"),CLUSTER_DISTRIBUTE_SYSTEM_ID("cluster.distributed.system.id"),
      ASYNC_QUEUE_BATCH_SIZE("gs.queue.batch-size"), ASYNC_QUEUE_INTERVAL("gs.queue.time-interval"),
      ASYNC_QUEUE_DISP_THREADS("gs.queue.dispatcher-threads"), MASTER_SERVICE_URL("master.service.url"),
      ASYNC_BQUEUE_SIZE("gs.block-queue.size"), ASYNC_THREAD_SIZE("gs.block-thread.size"),
      SECURITY_SYNC_RATE_HOURS("security.sync.rate.hours"), SECURITY_SYNC_SINGLE_INTERVAL("security.sync.single.interval"), MASTER_GRID_NAME("master.grid.name"),
      AS_MASTER_GRID("gs.grid.as.master"), ADF_REGISTER_TYPE("gs.grid.register.type"),
      CLIENT_POOL_SINGLEHOP("client.pool.pr-singlehop"), CLIENT_SENDING_SERVER_MODE("client.sending.server.mode"),
      CLIENT_POOL_TIMEOUT("client.pool.timeout"), CLIENT_POOL_RETRY("client.pool.retry-attemps"),
      CLIENT_POOL_MIN("client.pool.min-connections"), CLIENT_POOL_MAX("client.pool.max-connections"),
      CLIENT_FREE_TIMEOUT("client.pool.free-connection-timeout"),
      SOLR_COMMIT_MAXTIME(ConfigurationConstants.SOLR_COMMIT_MAXTIME),
      SOLR_COMMIT_BATCH_SIZE(ConfigurationConstants.SOLR_COMMIT_BATCH_SIZE),INDEXER_SOLR("gs.indexer.solr.enabled"),
      LUCENE_DIRECTORY(ConfigurationConstants.LUCENE_DIRECTORY),LUCENE_INTERVAL(ConfigurationConstants.LUCENE_INTERVAL),
      LUCENE_BATCHSIZE("indexer.lucene.batch-size"),LUCENE_PAGESIZE("indexer.lucene.page-size"),
      LUCENE_PREFETCH_COUNT("indexer.lucene.cache.prefetch-count"),
      LUCENE_OPLOG("indexer.lucene.oplog-size"),
      LUCENE_SENDING_WINDOW_SIZE("indexer.lucene.windowedsending.window_size"),
      LUCENE_SENDING_BUFFER_SIZE("indexer.lucene.windowedsending.buffer_size"),
      LUCENE_STATISTIC("indexer.lucene.statistic"),MASTER_CLINET_MULTIPLE_USER("master.client.multiple.user.enabled"),
      MASTER_CLIENT_POOL_TIMEOUT("master.client.pool.timeout"),MASTER_CLIENT_POOL_RETRY("master.client.pool.retry-attemps"),
      MASTER_CLIENT_POOL_MIN("master.client.pool.min-connections"), MASTER_CLIENT_POOL_MAX("master.client.pool.max-connections"),
      MASTER_CLIENT_FREE_TIMEOUT("master.client.pool.free-connection-timeout");

    private String proKey = null;
    
    private static Map<String, ITEMS> nameMap = new HashMap<>();
    private static Map<String, ITEMS> proKeyMap = new HashMap<>();
    
    static
    {
      for (ITEMS item : ITEMS.values())
      {
        nameMap.put(item.name(), item);
        proKeyMap.put(item.proKey, item);
        
      }
    }
    
    ITEMS(String name)
    {
      this.proKey = name;
    }
    
    public String getProKey()
    {
      return this.proKey;
    }
  }
  
  ;
  
}

